﻿/*! 基于封装的概念，一个工程可以作为一个整体，封装为一个模块。这个模块本身就是一系
 * 列的原模块（exe）连接而成。所有悬空的管脚都会被暴露出来。管脚的标号是局部的，
 * 所以模块外部的接口编号与模块内部是不冲突的。通路的标号却是全局的，因为通路代表了
 * 一组数据的完整性。这个EXE将被手工改为模块工程名一样的exe，比如模块为 sample.tbj,
 * 则EXE改为sample.exe，同时，需要涉及到的模块EXE列表另存为 sample.text 即可。
 *Based on the concept of encapsulation, a project can be packaged as a whole
 *  as a module. The module itself is a series of original modules (EXE)
 * connected. All dangling pins will be exposed.
 * The label of the pin is local, so the interface number outside the
 * module does not conflict with the inside of the module.
 * The path label is global, because the path represents the integrity of a
 * set of data. This exe will be manually changed to the same module project
 * name EXE, such as the module for SAMPLE.TBJ, then EXE to Sample.exe,
 * at the same time, need to refer to the module EXE list Save as Sample.text.
  */

#include <QByteArray>
#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTextStream>
#include <QProcess>
#include <QAtomicInteger>
#include <QDir>
#include <QSettings>
#include <stdio.h>
#include "core/taskcell.h"
#include "core/taskproject.h"
#include "watchdog/tbwatchdog.h"
#include "cmdlineparser.h"
#include "tb_interface.h"
#include "listen_thread.h"
#include "watchdog/profile_log.h"
QAtomicInteger<quint64>  g_totalrev (0), g_totalsent (0);
//读取模块 Read Module
int load_modules(QStringList newfms, taskCell * cell);
int load_default_modules(QString filename, taskCell * cell);
//显示JSON Show JSON
QJsonObject output_json(QString name,taskProject * proj);

void output_help(int argc, char *argv[])
{
	QFileInfo infoexe(argv[0]);
	QTextStream stmErr(stderr);

	stmErr <<"\nWelcome to Taskbus console module. Usage:\n"
		   <<"\n============\n"
		   <<infoexe.fileName()
		   <<"\n\t--information\tprint project's json interface.\n"
		   <<QString("\n\t--project=<project file>\tLoad project from file specified instead of %1.json.\n").arg(infoexe.completeBaseName())
		   <<QString("\n\t--loadfile=<module index text file>\tLoad modules from file specified instead of %1.text.\n").arg(infoexe.completeBaseName())
		   <<"\n------------\n"
		;

	stmErr.flush();

}
using namespace TASKBUS;
int main(int argc, char *argv[])
{
	QCoreApplication a(argc, argv);
	int ret = 0;
	QTextStream stmErr(stderr);
	QDir::setCurrent(a.applicationDirPath());
	QSettings settings(QCoreApplication::applicationFilePath()+".ini",QSettings::IniFormat);
//set Plugin PATH
#ifdef Q_OS_LINUX
	QString strExePath = qgetenv("PATH");
	QString newPath = QCoreApplication::applicationDirPath();
	if (strExePath.length())
	{
		newPath += ":";
		newPath += strExePath;
	}
	qputenv("PATH",newPath.toUtf8());
#else
	QString plgPath = settings.value("settings/QT_PLUGIN_PATH",QCoreApplication::applicationDirPath()).toString();
	QString uhdPath = settings.value("settings/UHD_PKG_PATH",QCoreApplication::applicationDirPath()+"/../uhd").toString();
	QDir dir_plg (plgPath), dir_uhd(uhdPath);
	plgPath = dir_plg.absolutePath();
	uhdPath = dir_uhd.absolutePath();
	QString strUHDPath = qgetenv("UHD_PKG_PATH");
	if (!strUHDPath.length())
	{
		strUHDPath = uhdPath;
		qputenv("UHD_PKG_PATH",strUHDPath.toUtf8());
	}

	QString strPluginPath = qgetenv("QT_PLUGIN_PATH");
	QString newPlgPath = plgPath;
	if (strPluginPath.length())
	{
		newPlgPath += ";";
		newPlgPath += strPluginPath;
	}
	qputenv("QT_PLUGIN_PATH",newPlgPath.toUtf8());

	QString strExePath = qgetenv("PATH");
	QString newPath =  QCoreApplication::applicationDirPath();
	if (strExePath.length())
	{
		newPath += ";";
		newPath += strExePath;
	}
	newPath += ";" + strUHDPath+"\\bin";
	qputenv("PATH",newPath.toUtf8());
#endif

	init_client();
	//init ProfileLog
	profile_log::init();
	//If you want to do profile test, please turn this on (true)
	profile_log::set_log_state(false);
	LOG_PROFILE("Program","Main Start.");

	//打印帮助 print help
	output_help(argc,argv);
	try
	{
		//接收线程 Receive thread
		reciv_thread * th_reciv = new reciv_thread(&a);
		send_object * send_obj = new send_object(&a);
		//加载工程文件 Loading project files
		const cmdlineParser args (argc,argv);
		QFileInfo info_exec(QCoreApplication::applicationFilePath());
		QFileInfo info_proj(info_exec.absolutePath() + "/" + info_exec.completeBaseName()+".tbj");
		QFileInfo info_text(info_exec.absolutePath() + "/" + info_exec.completeBaseName()+".text");

		if (args.contains("project"))
		{
			info_proj.setFile(QString::fromStdString(args.toString("project",QCoreApplication::applicationFilePath().toStdString())));
		}
		if (args.contains("loadfile"))
		{
			info_text.setFile(QString::fromStdString(args.toString("loadfile",QCoreApplication::applicationFilePath().toStdString())));
		}

		QString strModFileName = info_proj.absoluteFilePath();
		QString strLoadModules = info_text.absoluteFilePath();

		QDir::setCurrent(info_text.absolutePath());
		//1.预先读入模块 1. Pre-read module
		taskCell cell_all;
		stmErr <<"\nLoading modules from "<< strLoadModules <<"...";
		QCoreApplication::processEvents();
		int nTotalModules = load_default_modules(strLoadModules,&cell_all);
		while (nTotalModules <=0)
		{
			stmErr <<"\n No modules loaded from "<< strLoadModules<<"\n";
			stmErr.flush();

			if (args.contains("function"))
				throw QString("Need a text file contains all modules that  should be loaded first.");

			stmErr << "Please input module text filename(*.text):\n";
			stmErr.flush();
			QTextStream stmIn(stdin);
			QString fm = stmIn.readLine();
			if (fm.length()<=0)
				throw QString("Need a text file contains all modules that  should be loaded first.");

			info_text.setFile(fm);
			strLoadModules = info_text.absoluteFilePath();

			QDir::setCurrent(info_text.absolutePath());
			stmErr <<"\nLoading modules from "<< strLoadModules <<"...";
			QCoreApplication::processEvents();
			nTotalModules = load_default_modules(strLoadModules,&cell_all);

		}
		stmErr << nTotalModules <<" Loaded.\n";

		stmErr.flush();
		//2.加载工程 2. Loading the project
		int totalLoaded = 0;
		taskProject * prj = new taskProject(&a);
		while (totalLoaded<=0)
		{
			QFile fo(strModFileName);
			if (fo.open(QIODevice::ReadOnly))
			{
				QByteArray ar = fo.readAll();
				totalLoaded += prj->fromJson(ar,&cell_all);
				fo.close();
			}
			if (totalLoaded <=0)
			{
				stmErr <<"\n No functions loaded from "<< strModFileName<<"\n";
				stmErr.flush();
				if (args.contains("function"))
					throw QString("Need a tbj file contains running module.");
				stmErr << "Please input project filename(*.tbj):\n";
				stmErr.flush();
				QTextStream stmIn(stdin);
				QString fm = stmIn.readLine();
				if (fm.length()<=0)
					throw QString("Need a tbj file contains running module.");
				info_proj.setFile(fm);
				strModFileName = info_proj.absoluteFilePath();
			}
			QCoreApplication::processEvents();
		}


		//3.解释命令行参数 3. Interpreting command-line arguments
		if (args.contains("information"))
		{
			prj->setWrapperPrj(true);
			prj->refresh_idxes();

			QTextStream ste(stdout,QIODevice::WriteOnly);
			QJsonObject obj = output_json(info_proj.completeBaseName(),prj);
			QJsonDocument doc(obj);
			ste<<QString::fromStdString(doc.toJson().toStdString().c_str());
			ret = -1;
		}
		else if (args.contains("help",info_proj.completeBaseName().toStdString()))
		{
			QJsonObject obj = output_json(info_proj.completeBaseName(),prj);
			QJsonDocument doc(obj);
			puts(doc.toJson().toStdString().c_str());
			fprintf(stderr,"\nno function valid in func list.\n");
			ret = -1;
		}
		else if (args.contains("function"))
		{
			prj->setWrapperPrj(true);
			prj->refresh_idxes();
			//提取各个引脚的ID Extract the IDs of each pin
			QStringList lst_hang_ins = prj->idxout_hang_fullname2in().keys();
			foreach(QString instr,lst_hang_ins)
			{
				if (args.contains(instr.toStdString()))
					prj->set_outside_id_in(instr, args.toInt(instr.toStdString(),0));
				else if (args.contains(instr.toUtf8().toStdString()))
					prj->set_outside_id_in(instr, args.toInt(instr.toUtf8().toStdString(),0));
				else if (args.contains(instr.toLocal8Bit().toStdString()))
					prj->set_outside_id_in(instr, args.toInt(instr.toLocal8Bit().toStdString(),0));

			}
			QStringList lst_hang_outs = prj->idxout_hang_fullname2out().keys();
			foreach(QString outstr,lst_hang_outs)
			{
				if (args.contains(outstr.toStdString()))
					prj->set_outside_id_out(outstr, args.toInt(outstr.toStdString(),0));
				else if (args.contains(outstr.toUtf8().toStdString()))
					prj->set_outside_id_out(outstr, args.toInt(outstr.toUtf8().toStdString(),0));
				else if (args.contains(outstr.toLocal8Bit().toStdString()))
					prj->set_outside_id_out(outstr, args.toInt(outstr.toLocal8Bit().toStdString(),0));
			}

			//运行工程 run project
			a.connect (prj,&taskProject::sig_outside_new_package,send_obj, &send_object::send_package,Qt::QueuedConnection);
			a.connect (th_reciv, &reciv_thread::new_package,prj,&taskProject::slot_outside_recieved,Qt::QueuedConnection);
			a.connect (th_reciv, &reciv_thread::sig_quit,prj,
					  static_cast<void (taskProject::*)()>(&taskProject::stop_project),Qt::QueuedConnection);
			a.connect (prj, &taskProject::sig_stopped,&a,&QCoreApplication::quit,Qt::QueuedConnection);
		}

		if (ret == 0)
		{
			a.connect (prj, &taskProject::sig_iostat,[&](qint64 pid,quint64 pr,quint64 ps,quint64 br, quint64 bs)->void{
				stmErr << pid << "PR " <<pr <<",PS "<<ps<<",BR "<<br<<",BS "<<bs<<"\n";
				stmErr.flush();
			});
			a.connect (prj, &taskProject::sig_message,[&](QStringList namestr,QByteArrayList strMessages){
				QString prefix = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
				auto ap = namestr.begin();
				auto av = strMessages.begin();
				for (; ap!=namestr.end() && av!=strMessages.end()
					 ;++ap,++av)
				{
					stmErr<<prefix<<">"<<*ap<<QString::fromUtf8(*av)<<"\n";
					stmErr.flush();
				}

			});
			//run watch dog
			a.connect (&tb_watch_dog(), &tbWatchDog::sig_shutdown,&a,&QCoreApplication::quit,Qt::QueuedConnection);
			th_reciv->start();
			prj->start_project();
			ret = a.exec();
			prj->stop_project();

		}

		stmErr<<"Closing...";
		int uct = 0;
		while (++uct < 100)
		{
			QThread::msleep(30);
			QCoreApplication::processEvents();
		}

		th_reciv->terminate();
		th_reciv->deleteLater();
		send_obj->deleteLater();

	}
	catch (QString errMessage)
	{
		stmErr << "\n"<<errMessage<<"n";
	}
	catch (...)
	{
		stmErr << "\nUnknown error breaks out.\n";
	}

	QCoreApplication::processEvents();
	stmErr.flush();
	a.quit();
	return ret;
}

/*!
 * \brief load_default_modules 从默认加载脚本加载模块
 * load default modules from text script file
 * \param filename 脚本文件名，默认为"default.text" script file name.
 * \param cell 导入cell  import to cell
 */
int load_default_modules(QString filename, taskCell * cell)
{
	QFile fin(filename);
	if (fin.open(QIODevice::ReadOnly)==false)
		return 0;
	QTextStream st(&fin);
	QStringList lstNames;
	while (st.atEnd()==false)
	{
		lstNames<< st.readLine();
	}
	fin.close();
	return load_modules(lstNames,cell);

}

int load_modules(QStringList newfms, taskCell * cell)
{
	int total_loaded = 0;
	//QJSon
	foreach (QString newfm, newfms)
	{
		//首先试图找JSON文件.
		//Step 1, try to load from json
		QFileInfo infojs(newfm);
		QString abPath = infojs.absolutePath();
		QString abBName = infojs.completeBaseName();
		//Try JSON
		QString jsonfm = newfm+".json";
		QFile fjson(jsonfm), fjsonb(abPath+"/"+abBName+".json");
		QByteArray array ;
		if (fjson.open(QIODevice::ReadOnly))
		{
			array = fjson.readAll();
			fjson.close();
		}
		else if (fjsonb.open(QIODevice::ReadOnly))
		{
			array = fjsonb.readAll();
			fjsonb.close();
		}
		else//找不到JSON，则调用命令行
		//else , from cmdline
		{
			QProcess proc;
			QFileInfo infocmd(newfm);
			proc.setProgram(infocmd.absoluteFilePath());
			proc.setArguments(QStringList()<<"--information");
			proc.setWorkingDirectory(infocmd.absolutePath());
			proc.start();
			proc.waitForFinished(10000);
			array.append(proc.readAll());
			proc.kill();
		}
		if (array.size())
		{
			if (false==cell->initFromJson(array,newfm))
			{
				QString str = QString::fromLocal8Bit(array.data());
				QByteArray bt = QByteArray::fromStdString(str.toStdString());
				if (!cell->initFromJson(bt,newfm))
				{
					QTextStream stmErr(stderr);
					stmErr<<"\nLoad module "<<newfm<<" Failed.\n";
				}
				else
					++total_loaded;
			}
			else
				++total_loaded;
		}

	}
	return total_loaded;
}
QJsonObject output_json(QString name,taskProject * proj)
{
	QJsonObject obj_root;
	QJsonObject obj_func;
	obj_func["name"] = name;
	obj_func["parameters"] = QJsonObject();
	QJsonObject obj_insubs;
	QStringList lst_hang_ins = proj->idxout_hang_fullname2in().keys();
	foreach(QString instr,lst_hang_ins)
	{
		unsigned int in_id = proj->idxout_hang_fullname2in()[instr];
		unsigned int instance = proj->idxout_hang_in2instance()[in_id];
		const taskCell * cell = proj->vec_cells()[proj->idx_instance2vec()[instance]];
		const QString inname = proj->idxout_hang_in2name()[in_id];
		const QString tooltip = cell->in_subject_tooltip(cell->function_names().first(),inname);
		const QString type = cell->in_subject_item(cell->function_names().first(),inname,"type");
		QJsonObject obj_inter;
		obj_inter["type"] = type;
		obj_inter["tooltip"] = tooltip;
		obj_insubs[instr] = obj_inter;
	}
	obj_func["input_subjects"] = obj_insubs;


	QJsonObject obj_outsubs;
	QStringList lst_hang_outs = proj->idxout_hang_fullname2out().keys();
	foreach(QString outstr,lst_hang_outs)
	{
		unsigned int out_id = proj->idxout_hang_fullname2out()[outstr];
		unsigned int instance = proj->idxout_hang_out2instance()[out_id];
		const taskCell * cell = proj->vec_cells()[proj->idx_instance2vec()[instance]];
		const QString outname = proj->idxout_hang_out2name()[out_id];
		const QString tooltip = cell->out_subject_tooltip(cell->function_names().first(),outname);
		const QString type = cell->out_subject_item(cell->function_names().first(),outname,"type");
		QJsonObject obj_inter;
		obj_inter["type"] = type;
		obj_inter["tooltip"] = tooltip;
		obj_outsubs[outstr] = obj_inter;
	}
	obj_func["output_subjects"] = obj_outsubs;


	obj_root[name] = obj_func;

	return obj_root;

}
